
package bibliotheque.server;

import bibliotheque.client.remote.ToolsRemoteIS;
import bibliotheque.server.remote.ServerRemoteIT;
import bibliotheque.transaction.TransactionId;
import bibliotheque.client.remote.ClientRemoteIS;
import bibliotheque.nameserver.remote.NameServerRemoteIS;
import bibliotheque.document.DocumentId;
import bibliotheque.events.ErrorOccuredEvent;
import bibliotheque.events.ErrorOccuredListener;
import bibliotheque.events.StateChangedEvent;
import bibliotheque.events.StateChangedListener;
import bibliotheque.server.parser.ParserServer;
import bibliotheque.server.remote.ServerRemoteIC;
import bibliotheque.server.remote.ServerRemoteIN;
import bibliotheque.transaction.TransactionManager;
import bibliotheque.wave.WaveAction;
import bibliotheque.wave.WaveAgent;
import bibliotheque.wave.WaveAgentRemote;
import bibliotheque.wave.WaveId;
import java.io.File;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Semaphore;

public class Server extends WaveAgent implements ServerRemoteIN, ServerRemoteIC, ServerRemoteIT {
    
    protected class TimertaskHelloTimeout extends TimerTask{
        
        public TimertaskHelloTimeout() {
            super();
        }
        
        public void run() {
            mutex.acquireUninterruptibly();
            if(state == Server.State.START || state == Server.State.STOPPING) {
                ErrorOccured("TimertaskHelloTimeout occurs.\nBrutal stop.");
                BrutalStop();
            }
            mutex.release();
        }
    }
    
    public enum State {START, STOPPING, STOP};
    public enum SelectionType {WITHDRAW, CONSULT};
    
    protected ArrayList<StateChangedListener> state_changed_listener;
    protected ArrayList<ErrorOccuredListener> error_occured_listener;
    
    private Hashtable<WaveId,ArrayList<String>> listPing;
    
    protected Hashtable<String, ClientRemoteIS> clients;
    protected Hashtable<String, ToolsRemoteIS> tools;
    protected TransactionManager transaction_manager;
    protected State state;
    protected Semaphore mutex;
    protected NameServerRemoteIS name_server;
    
    protected Timer timer_hellotimeout;
    protected TimertaskHelloTimeout timertask_hellotimeout;
    
    public Server(String ip) throws RemoteException {
        super(ip);
        this.state_changed_listener = new ArrayList<StateChangedListener>();
        this.error_occured_listener = new ArrayList<ErrorOccuredListener>();
        this.listPing = new Hashtable<WaveId,ArrayList<String>>();
        
        this.state = State.STOP;
        this.mutex = new Semaphore(1);
        this.timer_hellotimeout = new Timer();
    }
    
    public void AddStateChangedListener(StateChangedListener listener) {
        this.state_changed_listener.add(listener);
    }
    
    public void RemoveStateChangedListener(StateChangedListener listener) {
        this.state_changed_listener.remove(listener);
    }
    
    public void StateChanged(String state){
        StateChangedEvent event = new StateChangedEvent(this, state);
        for(int i=0;i<this.state_changed_listener.size();i++) {
            this.state_changed_listener.get(i).StateChanged(event);
        }
    }
    
    public void AddErrorOccuredListener(ErrorOccuredListener listener) {
        this.error_occured_listener.add(listener);
    }
    
    public void RemoveErrorOccuredListener(ErrorOccuredListener listener) {
        this.error_occured_listener.remove(listener);
    }
    
    public void ErrorOccured(String error){
        ErrorOccuredEvent event = new ErrorOccuredEvent(this, error);
        for(int i=0;i<this.error_occured_listener.size();i++) {
            this.error_occured_listener.get(i).ErrorOccured(event);
        }
    }
    
    protected void BrutalStop() {
        if(this.timertask_hellotimeout != null) this.timertask_hellotimeout.cancel();
        this.ErrorOccured("Brutal stop.");
        try {
            this.name_server.Unsubscribe(this.GetIp());
        } catch (Exception ex) {
            
        }
        this.state = State.STOP;
        this.StateChanged(this.state.toString());
    }
    
    public DocumentId GetNewDocumentId() {
        return(this.transaction_manager.GetNewDocumentId(this.GetIp()));
    }
    
    public void StartService(File file, String service, String ip, int port) {
        Registry registry;
        Remote stub;
        try {
            this.mutex.acquireUninterruptibly();
            super.mutex.acquireUninterruptibly();
            if(this.state == State.START) throw new Exception("StartService failed cause server state is started.");
            if(this.state == State.STOPPING) throw new Exception("StartService failed cause server state is stopping.");
            
            this.neighbours = new ArrayList<WaveAgentRemote>();
            this.transaction_manager = new TransactionManager();
            this.clients = new Hashtable<String, ClientRemoteIS>();
            this.tools = new Hashtable<String, ToolsRemoteIS>();
            
            try {
                this.transaction_manager.SetDocuments(ParserServer.GetDocuments(file, this));
            } catch (Exception ex) {
                throw new Exception("StartService failed cause parse failed.");
            }
            
            try {
                registry = LocateRegistry.getRegistry(ip, port);
            } catch (Exception ex) {
                throw new Exception("StartService failed cause the RMIRegistry cannot be find.");
            }
            
            try {
                stub = registry.lookup(service);
            } catch (Exception ex) {
                throw new Exception("StartService failed cause the service " + ex.getMessage() + " cannot be find.");
            }
            
            if(!(stub instanceof NameServerRemoteIS)) throw new Exception("StartService failed cause " + service + " is not a valid service.");
            this.name_server = (NameServerRemoteIS)stub;
            
            try {
                this.name_server.Subscribe(this.GetIp(), this);
            } catch (Exception ex) {
                throw new Exception("StartService failed cause the nameserver returned an error.");
            }
            
            this.state = State.START;
            this.StateChanged(this.state.toString());
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
        } finally {
            super.mutex.release();
            this.mutex.release();
        }
    }
    
    public void StopService() {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOPPING) throw new Exception("StopService failed cause server state is stopping.");
            if(this.state == State.STOP) throw new Exception("StopService failed cause server state is stopped.");
            if(this.transaction_manager.GetNbUseDocument() != 0) {
                this.state = State.STOPPING;
                this.StateChanged(this.state.toString());
            } else if(this.GetNbWaves() != 0) {
                this.name_server.Unsubscribe(this.GetIp());
                this.state = State.STOPPING;
                this.StateChanged(this.state.toString());
            } else {
                this.name_server.Unsubscribe(this.GetIp());
                this.state = State.STOP;
                this.StateChanged(this.state.toString());
            }
            
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
        } finally {
            this.mutex.release();
        }
    }
    
    public void AddNeighbour(WaveAgentRemote server) throws RemoteException {
        this.neighbours.add(server);
    }
    
    public void RemoveNeighbour(WaveAgentRemote server) throws RemoteException {
        this.neighbours.remove(server);
    }
    
    public void Hello(long timeout) throws RemoteException {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOP) throw new Exception("Hello failed cause server state is stopped.");
            this.Initiate(new HelloAction(timeout));
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            this.mutex.release();
        }
    }
    
    public void Stop() throws RemoteException {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOPPING) throw new Exception("Stop failed cause server state is stopping.");
            if(this.state == State.STOP) throw new Exception("Stop failed cause server state is stopped.");
            
            Initiate(new StopAction());
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    protected void WaveError(String error) {
        ErrorOccured("A wave error occurs.\n" + error);
        if(this.state != State.STOP) this.BrutalStop();
    }
    
    public void Consult(String id, ClientRemoteIS client, ArrayList<String> filtre) throws RemoteException{
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOPPING) throw new Exception("Consult failed cause server state is stopping.");
            if(this.state == State.STOP) throw new Exception("Consult failed cause server state is stopped.");
            
            if(!this.clients.containsKey(id)) this.clients.put(id, client);
            TransactionId tid = this.transaction_manager.GetNewTransactionId(id);
            ResolvAction action = new ResolvAction(tid, filtre, SelectionType.CONSULT);
            this.Initiate(action);
            
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Withdraw(String id, ClientRemoteIS client, ArrayList<String> filtre) throws RemoteException {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOPPING) throw new Exception("Withdraw failed cause server state is stopping.");
            if(this.state == State.STOP) throw new Exception("Withdraw failed cause server state is stopped.");
            
            if(!this.clients.containsKey(id)) this.clients.put(id, client);
            TransactionId tid = this.transaction_manager.GetNewTransactionId(id);
            ResolvAction action = new ResolvAction(tid, filtre, SelectionType.WITHDRAW);
            this.Initiate(action);
            
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Free(TransactionId tid, DocumentId did) throws RemoteException{
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOP) throw new Exception("Free failed cause server state is stopped.");
            
            try{
                if (this.transaction_manager.FreeDocument(tid, did)){
                    this.clients.get(tid.GetClient()).SetFreeResult(true, did);
                } else {
                    FreeAction action = new FreeAction(tid, did);
                    this.Initiate(action);
                }
            } catch (RemoteException ex) {
                throw ex;
            } catch (Exception ex) {
                InvalidateAction action = new InvalidateAction(tid);
                this.Initiate(action);
            }
        } catch (RemoteException ex) {
            this.ErrorOccured("Free failed cause the client return an error");
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Read(TransactionId tid, DocumentId did) throws RemoteException{
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOP) throw new Exception("Read failed cause server state is stopped.");
            
            try{
                String s = this.transaction_manager.GetDocumentContent(tid, did);
                if (s != null){
                    this.clients.get(tid.GetClient()).SetReadResult(did, s);
                } else {
                    ReadAction action = new ReadAction(tid, did);
                    this.Initiate(action);
                }
            } catch (RemoteException ex) {
                throw ex;
            } catch (Exception ex) {
                InvalidateAction action = new InvalidateAction(tid);
                this.Initiate(action);
            }
        } catch (RemoteException ex) {
            this.ErrorOccured("Read failed cause the client return an error");
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Validate(TransactionId tid) throws RemoteException{
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOP) throw new Exception("Validate failed cause server state is stopped.");
            
            SelectionType type = this.transaction_manager.GetTransactionType(tid);
            WaveAction action;
            if (type == SelectionType.CONSULT)
                action =  new InvalidateAction(tid);
            else
                action =  new PreValidateAction(tid);
            this.Initiate(action);
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Invalidate(TransactionId tid) throws RemoteException{
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOP) throw new Exception("Invalidate failed cause server state is stopped.");
            
            InvalidateAction action = new InvalidateAction(tid);
            this.Initiate(action);
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Add(ClientRemoteIS client, String title, String summary, String content) throws RemoteException {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOPPING) throw new Exception("Read failed cause server state is stopping.");
            if(this.state == State.STOP) throw new Exception("Read failed cause server state is stopped.");
            
            if (this.transaction_manager.AddDocument(title,summary,content,this.GetIp()))
                client.SetAddResult(true);
            else client.SetAddResult(false);
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    
    public void Ping(String ip, ToolsRemoteIS tool) throws RemoteException{
        if(!this.tools.containsKey(ip)) this.tools.put(ip,tool);
        this.Initiate(new PingAction(ip));
        
    }
    
    public ArrayList<String> GetPingList(WaveId waveId){
        if(this.listPing.containsKey(waveId)){
            return this.listPing.get(waveId);
        } else {
            this.ErrorOccured("Error getting pingList");
            return null;
        }
        
    }
    
    public void AddInPingList(WaveId waveId, String ip){
        if (!this.listPing.containsKey(waveId)){
            this.listPing.put(waveId,new ArrayList<String>());
        }
        
        if (this.listPing.containsKey(waveId)){
            if(!this.listPing.get(waveId).contains(ip))
                this.listPing.get(waveId).add(ip);
        } else{
            this.ErrorOccured("Error adding an IP in ping list");
        }
        
    }
    
    public void RemovePingList(WaveId waveId){
        this.listPing.remove(waveId);
    }
    
    public void RemoveTool(String ip) {
        if(this.tools.containsKey(ip)) this.tools.remove(ip);
    }
    
}
